// standard headers
#include <istream>
#include <vector>
#include <string>
#include <fstream>
#include <sstream>
#include <iostream>
#include <algorithm>
#include <map>
#include <cstdlib>

// boost headers
#include <boost/format.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/filesystem/path.hpp>
#include <boost/regex.hpp>

// components headers
#include <bcpp_read_seq.h>
#include <print_error.h>
#include <print_sequence.h>
#include <portable_timer.h>
#include <progress_indicator.h>
#include "get_arg.h"
#define DEBUG_LEVEL 6
#include <debug_func.h>

using BioCPP::t_collect_sequences;
using BioCPP::t_collect_sequences_report_errors_to_strm;
using BioCPP::eSEQ_NODUPLICATES;
using BioCPP::eSEQ_DUPLICATES;



using boost::format;
using boost::lexical_cast;
using boost::filesystem::path;
using std::vector;
using std::ostringstream;

using std::string;
using std::fill;
using std::ofstream;
using std::cout;
using std::cin;	
using std::cerr;	
using std::ifstream;
using std::map;
using std::pair;
using std::istream;
using std::max;
using std::min;


//****************************************************************************************

//	read_names_from_file

//****************************************************************************************
void read_names_from_stdin(	vector<string>&				names,
							const t_collect_sequences&	seq_db,
							t_program_args&				args)
{
	if (args.sequences_file.length())
	{
		// open file or die
		args.VERBOSE() << "\tMatching supplied sequence names...\n";

		// count duplicates in file
		unsigned cnt_valid_names_duplicates = 0;

		t_index_string names_from_file;
		string name;
		while (getline(std::cin, name))
		{
			//
			//	make sure this valid_name has not been added before
			//
			bool added;
			names_from_file.index(name, added);
			if (!added)
				cnt_valid_names_duplicates++;
			else
				names.push_back(name);
		}

		if (names.size() == 0)
			args.VERBOSE() << "\nWARNING\n\tNo names have been specified\n\n";
		else
		{
			args.VERBOSE()	<< "\t" << names.size() + cnt_valid_names_duplicates
							<< " sequence names.\n";
			if (cnt_valid_names_duplicates)
				args.VERBOSE()	<< "\nWARNING\n\t" << cnt_valid_names_duplicates
								<< " of the supplied names were duplicates.\n\n";
		}
	}
	else
		// no names in file, just use all sequence database names
		names.insert(names.end(), seq_db.names().begin(),  seq_db.names().end());
}


//****************************************************************************************

//	filter_indices_by_names

//****************************************************************************************
void filter_indices_by_names(	const t_collect_sequences&	seq_db,
								const vector<string>&		names,
						   		vector<unsigned>&			valid_indices,
								t_program_args&				args)
{
	for (unsigned i = 0; i < names.size(); ++i)
	{
		// add the indices of all the sequences which have this name
		std::vector<unsigned> indices;
		seq_db.get_indices_of_name(names[i], indices);
		for (unsigned i = 0; i < indices.size(); ++i)
			valid_indices.push_back(indices[i]);
	}
}

//****************************************************************************************

//	print_sequences

//****************************************************************************************
void print_sequences(	const t_collect_sequences&	seq_db,
						vector<unsigned>&			valid_indices,
						t_program_args&				args)
{
	unsigned line_wrap = args.line_wrap;
	if (!line_wrap)
		line_wrap = unsigned (-1);
	for (unsigned i = 0; i < valid_indices.size(); ++i)
	{
		print_fasta(cout, seq_db.accession(valid_indices[i]),
						seq_db.sequence(valid_indices[i]), line_wrap);
	}

}


//****************************************************************************************

//	read_seq

//****************************************************************************************
struct t_seq_db_report_errors :
		public t_collect_sequences_report_errors_to_strm
{
	unsigned cnt_no_acc_name_parsed;
	void no_matching_seq_id(unsigned line, const std::string& regex_str)
	{++cnt_no_acc_name_parsed;}
	t_seq_db_report_errors(std::ostream& CERR_) :
			t_collect_sequences_report_errors_to_strm(CERR_),
			cnt_no_acc_name_parsed(0)
		 {	}
};

void read_seq_from_strm(istream& seq_strm, t_collect_sequences& seq_db, t_program_args& args)
{
	// if no regular expression specified, take the first non-whitespace
	string regex_str = args.regex;
	if (!regex_str.length())
		regex_str = "(\\S+)";

	t_seq_db_report_errors report_errors(args.CERR());
	{
		t_progress_indicator dots(args.VERBOSE(), 10);
		seq_db.init_from_stream(seq_strm, regex_str, dots, report_errors, string(), false);
	}	
	if (seq_db.size() + report_errors.cnt_no_acc_name_parsed == 0)
		throw std::runtime_error("Empty sequence file supplied.");

	args.VERBOSE() << "\t" << seq_db.size() + report_errors.cnt_no_acc_name_parsed
				<< " sequences read\n";
	if (report_errors.cnt_no_acc_name_parsed)
		args.VERBOSE() << "\t" << report_errors.cnt_no_acc_name_parsed
						<< " sequences did not have accessions matching the regular "
							"expression and were discarded.\n";
}

void read_seq(t_collect_sequences& seq_db, t_program_args& args)
{
	// get sequences from the specified file
	args.VERBOSE() << "\tReading sequences from <" << args.sequences_file << ">...\n";
	ifstream seq_strm(args.sequences_file.c_str());
	if (!seq_strm)
		throw std::runtime_error("Could not open the file <"  + args.sequences_file + ">" );
	read_seq_from_strm(seq_strm, seq_db, args);

}


//8888888888888888888888888888888888888888888888888888888888888888888888888888888888888888

//	main

//8888888888888888888888888888888888888888888888888888888888888888888888888888888888888888
struct op_sort_indices_by_name
{
	t_collect_sequences& seq_db;
	op_sort_indices_by_name(t_collect_sequences& seq_db_):seq_db(seq_db_){}
	bool operator () (unsigned i, unsigned j) const
	{	return seq_db.name(i) < seq_db.name(j);}
};
int main (int argc, char *argv[])
{
	t_program_args args;
	try
	{

		std::ios::sync_with_stdio(false);
		
		if (!process_command_line_options(argc, argv, args))
			return 1;

		//
		// make sure regex surrounded by brackets
		//
		boost::regex brackets("\\(.*\\)");
		if (!boost::regex_search(args.regex, brackets))
		{
			std_print_warning("Missing brackets around the regular expression have been "
								"added back in.\n");
			args.regex = "(" + args.regex + ")";
		}

	
	
		//
		// read sequences
		//
		t_collect_sequences seq_db(eSEQ_DUPLICATES);
		read_seq(seq_db, args);
		if (seq_db.size() == 0)
			return 0;
	
		//
		// read names or use all names from the sequences
		//
		vector<string>				names;
		if (args.names)
			read_names_from_stdin(names, seq_db, args);
	
		
	
		vector<unsigned> valid_indices;


		//
		// add selected indices if fall within range
		//
		unsigned cnt_seq = seq_db.size();
		bool indices_out_of_range = false;
		for (vector<unsigned>::const_iterator i = args.pos_indices.begin(),
					e = args.pos_indices.end(); i != e; ++i)
			if (*i < cnt_seq)
				valid_indices.push_back(*i);
			else
				indices_out_of_range = true;
		if (indices_out_of_range)
			std_print_warning(args.VERBOSE(), "Some of the specified indices were larger "
						"than the number of sequences and will be ignored.");


		//
		// only select names matching those in file
		//
		filter_indices_by_names(seq_db, names, valid_indices, args);
	
		args.VERBOSE() << "\t" << valid_indices.size() << " sequences extracted.\n";


		//
		// select everything if no names or indices selected
		//
		if (!valid_indices.size() && !args.names && args.pos_indices.empty())
			for (unsigned i = 0; i < seq_db.size(); ++i)
				if (seq_db.name(i).length())
					valid_indices.push_back(i);



		//
		//	some names alphabetically
		//
		if (args.alpha)
		{
			args.VERBOSE() << "\tSort sequence names by alphabetical order...\n";
			sort(valid_indices.begin(), valid_indices.end(), op_sort_indices_by_name(seq_db));
		}


		//
		// print sequences
		//
//		print_sequences(seq_db, valid_indices, args);
		seq_db.print_sequences(args.output_format, cout,  valid_indices, args.line_wrap);


		args.VERBOSE() << "\n\tAll finished!!\n\n";
		
	}
	catch (std::exception& e)
	{
		std_print_error(args.CERR(), e.what());
		return 1;
	}
	catch(...)
	{
		std_print_error(args.CERR(), "Unknown exception");
		return 1;
	}

	return 0;
}

